package edu.udlap.spring2008.is300.finalexam.app.mvc.model;

import edu.udlap.spring2008.is300.finalexam.app.mvc.model.database.DatabaseFacade;
import edu.udlap.spring2008.is300.finalexam.app.mvc.model.database.filters.AppImageRecordFilterFavorite;
import edu.udlap.spring2008.is300.finalexam.app.mvc.model.database.filters.AppImageRecordFilterId;
import edu.udlap.spring2008.is300.finalexam.app.mvc.model.database.filters.AppImageRecordFilterNullFilter;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.objects.CategoryRecord;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.objects.FormatRecord;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.objects.ImageRecord;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.filters.ImageRecordFilter;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.Model;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.objects.PrivacyLevelRecord;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.model.database.objects.SizeRecord;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.favorites.FavoritesChangedListener;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.favorites.FavoritesChangedObservableHelper;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.imageset.ImageSetChangedListener;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.imageset.ImageSetChangedObservableHelper;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.selection.SelectionChangedListener;
import edu.udlap.spring2008.is300.finalexam.framework.mvc.observerobservable.selection.SelectionChangedObservableHelper;
import java.util.ArrayList;

/**
 * A quick note: remember that all operations must complete before calling any
 * updates/events, so that no partial change in the states is found by any
 * listeners.
 * 
 * @author Ruslán Ledesma Garza
 */
public class AppModel implements Model
{

    protected DatabaseFacade database_;
    protected ImageRecordFilter imageRecordFilter_;
    protected ImageSetChangedObservableHelper imageSetChangedObservableHelper_;
    protected SelectionChangedObservableHelper selectionChangedObservableHelper_;
    protected FavoritesChangedObservableHelper favoritesChangedObservableHelper_;
    
    protected final int NO_SELECTED_IMAGE = -1;
    
    /**
     * The id of the currently selected record. If -1, then there is no
     * no selection.
     */
    protected int selectedImageRecordId_ = NO_SELECTED_IMAGE;
    
    // <editor-fold desc="Object overriden methods...">
    
    @Override
    protected void finalize() throws Throwable
    {
        super.finalize();
        stop();
    }
    
    // </editor-fold>
    
    // <editor-fold desc="Model interface...">
    
    @Override
    public void initialize()
    {
        database_ =
            new DatabaseFacade(
                new org.apache.derby.jdbc.EmbeddedDriver(),
                "jdbc:derby:db/images",
                "img",
                "img");
        
        //System.setProperty("derby.system.home", "db");
        
        database_.registerDriver();
        database_.connect();
        
        ///create observer helpers...
        imageSetChangedObservableHelper_ = new ImageSetChangedObservableHelper(this);
        selectionChangedObservableHelper_ = new SelectionChangedObservableHelper(this);
        favoritesChangedObservableHelper_ = new FavoritesChangedObservableHelper(this);
        
        ///reset selected image...
        selectedImageRecordId_ = NO_SELECTED_IMAGE;
        
        ///set a null image filter...
        imageRecordFilter_ = new AppImageRecordFilterNullFilter();
    }

    @Override
    public void stop()
    {
        if(database_ != null)
        {
            database_.closeConnection();
            database_.unregisterDriver();

            database_ = null;
        }
        
        if(imageSetChangedObservableHelper_ != null)
            imageSetChangedObservableHelper_ = null;
        
        if(selectionChangedObservableHelper_ != null)
            selectionChangedObservableHelper_ = null;
        
        if(favoritesChangedObservableHelper_ != null)
            favoritesChangedObservableHelper_ = null;
    }
    
    @Override
    public boolean addImageRecord(ImageRecord imageRecord)
    {
        boolean result = database_.addImageRecord(imageRecord);
        
        if(result)
        {
            raiseImageSetChangedEvent(null);
            
            if(imageRecord.isFavorite())
                raiseFavoritesChangedEvent(null);
        }
        
        return result;
    }

    @Override
    public boolean deleteSelectedImageRecord()
    {
        boolean result = false;
        boolean wasFavorite = false;
        
        ///find out if the selected image is favorite...
        ImageRecord imageRecord = getSelectedImageRecord();
        if(imageRecord != null)
            wasFavorite = imageRecord.isFavorite();
        
        result = database_.deleteImageRecord(selectedImageRecordId_);
        
        if(result)
        {
            selectedImageRecordId_ = NO_SELECTED_IMAGE;
            raiseImageSetChangedEvent(null); ///the set has changed...
            raiseSelectionChangedEvent(null); ///the selection has changed too...
            
            if(wasFavorite)
                raiseFavoritesChangedEvent(null);
        }
        
        return result;
    }

    @Override
    public boolean updateImageRecord(ImageRecord imageRecord)
    {   
        
        boolean updatedImageOriginallyMatchedCriteria = false;
        boolean updatedImageNowMatchesCriteria = false;
        boolean updatedImageWasTheSelectedOne =
                imageRecord.getId() == selectedImageRecordId_;
        boolean updatedImageOriginalFavoriteStatus = false;
        boolean updatedImageCurrentFavoriteStatus = imageRecord.isFavorite();
        
        ///find out the original favorite status of the image to be updated...
        {
            ImageRecord originalImageRecord =
                    getImageRecord(imageRecord.getId());
            
            if(originalImageRecord != null)
                updatedImageOriginalFavoriteStatus =
                        originalImageRecord.isFavorite();
        }
        
        ///find out if the updated image is in the set of matching images...
        {
            ///decorate existing filter in order to find out
            ///if the updated image is among the criteria-matching
            ///images...
            ImageRecordFilter filter =
                new AppImageRecordFilterId(
                    imageRecordFilter_,
                    imageRecord.getId(),
                    false);

            ///find out what we already said...
            updatedImageOriginallyMatchedCriteria =
                database_.hasMatchingImages(filter.getSqlConstraints());
        }
        
        ///perform update...
        if(!database_.updateImageRecord(imageRecord))
            return false;

        ///find out if the updated image is in the set of matching images...
        {
            ///decorate existing filter in order to find out
            ///if the updated image is among the criteria-matching
            ///images...
            ImageRecordFilter filter =
                new AppImageRecordFilterId(
                    imageRecordFilter_,
                    imageRecord.getId(),
                    false);

            ///find out what we already said...
            updatedImageNowMatchesCriteria =
                database_.hasMatchingImages(filter.getSqlConstraints());
        }
        
        ///if the updated image is the currently selected one and
        ///it is not (more preciselly "no longer") among the
        ///matching ones...
        if(updatedImageWasTheSelectedOne &&
                    !updatedImageNowMatchesCriteria)
            selectedImageRecordId_ = NO_SELECTED_IMAGE;
        

        
        ///once all changes in the model have happened, we have to raise the
        ///appropriate events...
        
        ///if the updated image was originally in the matching-criteria set...
        if(updatedImageOriginallyMatchedCriteria ||
                    updatedImageNowMatchesCriteria)
            raiseImageSetChangedEvent(null); ///the image set has changed...
        
        ///if the updated image is the currently selected one, then...
        if(updatedImageWasTheSelectedOne)
            raiseSelectionChangedEvent(null); ///the selection has changed...
            
        ///if the updated image changed its favorite status...
        if(updatedImageOriginalFavoriteStatus !=
                    updatedImageCurrentFavoriteStatus)
            raiseFavoritesChangedEvent(null);
        
        return true;
    }

    @Override
    public boolean setSelectedImageRecord(int id)
    {
        ///if the selected image is the same as the requested, do nothing...
        if(id == selectedImageRecordId_)
            return false;
        
        boolean result = false;
        
        ///decorate existing filter in order to find out
        ///if the required id is among the criteria-matching
        ///images...
        ImageRecordFilter filter =
            new AppImageRecordFilterId(
                imageRecordFilter_,
                id,
                false);
        
        ///find out what we already said...
        if(database_.hasMatchingImages(filter.getSqlConstraints()))
        {
            selectedImageRecordId_ = id;
            raiseSelectionChangedEvent(null);
            result = true; ///the selection operation has succedeed...
        }
        
        return result;
    }

    @Override
    public boolean setImageRecordFilter(ImageRecordFilter imageRecordFilter)
    {
        ///if the imagerecords are equal, do nothing...
        if(imageRecordFilter_.equals(imageRecordFilter))
            return false;
        
        int previouslySelectedImage = selectedImageRecordId_;
        
        ///if the imageRecordFilter is null...
        if(imageRecordFilter == null)
            imageRecordFilter_ = new AppImageRecordFilterNullFilter();
        else
            imageRecordFilter_ = imageRecordFilter;
        
        if(!setSelectedImageRecord(selectedImageRecordId_))
            selectedImageRecordId_ = NO_SELECTED_IMAGE;
        
        
        ///once all changes in the model have happened, we have to raise the
        ///appropriate events...
        
        ///notify that there has been a change in the image set...
        raiseImageSetChangedEvent(null);
        
        ///if the selection changed, then...
        if(previouslySelectedImage != selectedImageRecordId_)
            ///notify that there has been a change in the selected image...
            raiseSelectionChangedEvent(null);
        
        return true;
    }
    // </editor-fold>
    
    // <editor-fold desc="ModelState interface...">
    
    @Override
    public ArrayList<ImageRecord> getAllImageRecords()
    {
        return database_.getMatchingImages("");
    }

    @Override
    public ArrayList<ImageRecord> getMatchingImageRecords()
    {   
        return database_.getMatchingImages(
            imageRecordFilter_.getSqlConstraints());
    }

    @Override
    public ArrayList<ImageRecord> getFavoriteImageRecords()
    {
        ImageRecordFilter imageRecordFilter =
            new AppImageRecordFilterFavorite(null, true, false);
        
        return database_.getMatchingImages(
            imageRecordFilter.getSqlConstraints());
    }

    @Override
    public ArrayList<CategoryRecord> getCategoryRecords()
    {
        return database_.getCategoryRecords();
    }

    @Override
    public ArrayList<SizeRecord> getSizeRecords()
    {
        return database_.getSizeRecords();
    }

    @Override
    public ArrayList<PrivacyLevelRecord> getPrivacyLevelRecords()
    {
        return database_.getPrivacyLevelRecords();
    }

    @Override
    public ArrayList<FormatRecord> getFormatRecords()
    {
        return database_.getFormatRecords();
    }

    @Override
    public ImageRecord getSelectedImageRecord()
    {
        ///create an image record filter with the currently selected image id...
        ImageRecordFilter imageRecordFilter =
            new AppImageRecordFilterId(
                imageRecordFilter_,
                selectedImageRecordId_,
                false);
        
        ///get all matching images...
        ArrayList<ImageRecord> imageRecords =
            database_.getMatchingImages(imageRecordFilter.getSqlConstraints());
        
        ///if there is one and only one matching image, return its record...
        if(imageRecords.size() == 1)
            return imageRecords.get(0);
        else
            return null;
    }

    @Override
    public ImageRecord getImageRecord(int id)
    {
        ///create an image record filter with the currently selected image id...
        ImageRecordFilter imageRecordFilter =
            new AppImageRecordFilterId(
                null,
                id,
                false);
        
        ///get all matching images...
        ArrayList<ImageRecord> imageRecords =
            database_.getMatchingImages(imageRecordFilter.getSqlConstraints());
        
        ///if there is one and only one matching image, return its record...
        if(imageRecords.size() == 1)
            return imageRecords.get(0);
        else
            return null;
    }

    @Override
    public CategoryRecord getCategoryRecord(int id) 
    {
        return database_.getCategoryRecord(id);
    }

    @Override
    public SizeRecord getSizeRecord(int id)
    {
        return database_.getSizeRecord(id);
    }

    @Override
    public PrivacyLevelRecord getPrivacyLevelRecord(int id)
    {
        return database_.getPrivacyLevelRecord(id);
    }

    @Override
    public FormatRecord getFormatRecord(int id)
    {
        return database_.getFormatRecord(id);
    }

    @Override
    public ImageRecordFilter getImageRecordFilter()
    {
        return imageRecordFilter_;
    }
    // </editor-fold>
    
    // <editor-fold desc="Observable interface...">
    
    public void addImageSetChangedListener(ImageSetChangedListener observer)
    {
        imageSetChangedObservableHelper_.addImageSetChangedListener(observer);
    }

    public void removeImageSetChangedListener(ImageSetChangedListener observer)
    {
        imageSetChangedObservableHelper_.removeImageSetChangedListener(observer);
    }

    public void raiseImageSetChangedEvent(Object args)
    {
        imageSetChangedObservableHelper_.raiseImageSetChangedEvent(args);
    }

    public void addSelectionChangedListener(SelectionChangedListener observer)
    {
        selectionChangedObservableHelper_.addSelectionChangedListener(observer);
    }

    public void removeSelectionChangedListener(SelectionChangedListener observer)
    {
        selectionChangedObservableHelper_.removeSelectionChangedListener(observer);
    }

    public void raiseSelectionChangedEvent(Object args)
    {
        selectionChangedObservableHelper_.raiseSelectionChangedEvent(args);
    }

    public void addFavoritesChangedListener(FavoritesChangedListener observer)
    {
        favoritesChangedObservableHelper_.addFavoritesChangedListener(observer);
    }

    public void removeFavoritesChangedListener(FavoritesChangedListener observer)
    {
        favoritesChangedObservableHelper_.removeFavoritesChangedListener(observer);
    }

    public void raiseFavoritesChangedEvent(Object args)
    {
        favoritesChangedObservableHelper_.raiseFavoritesChangedEvent(args);
    }
    
    // </editor-fold>
}
